#ifndef BUFFER_WRAPPER
#define BUFFER_WRAPPER

#include <vector>
#include <deque>
#include <cassert>

#include "Handle.h"
#include "../PoolMemoryManager/PoolMemoryManager.h"
#include "../PoolMemoryManager/PoolGroupManager.h"
#include "../StackMemorySystem/StackMemorySystem.h"


// BufferWrapper - wrapper class for different buffer alocation strategies: Pool-based, Stack-based and Managed Pool-based
class BufferWrapper {
public:
	enum BufferType {
		poolBuffer,
		stackBuffer,
		managedPoolBuffer,
	};

private:

	// pointers to the actual buffer being wrapped, one for each wrappable class
	union {
		void* m_Buffer;
		PoolMemoryManager* m_PoolBuffer;
		StackMemorySystem* m_StackBuffer;
		PoolGroupManager* m_ManagedBuffer;
	};

	typedef std::vector<void*> PointerVector;
	typedef std::deque<dataId_t> DataIdQueue;
	typedef std::vector<unsigned int> MagicVector;

	BufferType m_Type;				// type of the wrapped buffer
	MagicVector m_MagicNumbers;		// magic numbers corresponding to handle ids, 
									// used to check if the handle is referring to the right object
	PointerVector m_IdToPointer;	// pointers to memory where objects are stored
	DataIdQueue m_FreeIds;			// free handle data indices for this buffer

	// prepares for wrapping a buffer of a given capacity, sets up MagicNumvers, IdToPointer, FreeIds lists
	void init(size_t capacity);

	BufferWrapper(const BufferWrapper& other) {}

public:
		
	// frees the buffer, removes the wrapped buffer and clears all the lists set-up in init()
	void free();
	// checks if there's no wrapped buffer
	inline bool isFree() { return m_Buffer == 0; }
		
	// create*Buffer functions create buffers of different types:

	// creates a pool-based buffer of elementSize*capacity bytes
	void createPoolBuffer(size_t elementSize, size_t capacity);

	// creates a stack-based buffer of byteAlignment*capacity bytes
	void createStackBuffer(size_t byteAlignment, size_t capacity);

	// creates a managed pool-based buffer from a given list of single pool-based buffers
	void createManagedBuffer();
	
	// adds a pool to a managed buffer
	void addPoolToManage(size_t elementSize, size_t capacity);

	// accessing data:

	// creates a new object of a given type in the buffer and returns it's index for the handle
	template<typename DATA> DATA* acquire(dataId_t& id);

	// sets the magic number after initializing a handle
	void setMagic(dataId_t id, unsigned int magicNumber) { 
		m_MagicNumbers[id] = magicNumber; 
	}

	// fetches a pointer to an object referenced by handle data index
	template<typename DATA> DATA* dereference(dataId_t id, unsigned int magicNumber);

	// frees memory used by the object referenced by handle data index
	// for Stack-based strategy, it will also free all the memory above the given index
	void release(dataId_t id, unsigned int magicNumber);

	// constructor
	BufferWrapper();
	// destructor
	~BufferWrapper();

};



template<typename DATA> DATA* BufferWrapper::acquire(dataId_t& id) {
	void* pointer = 0;
	assert(!isFree() && "Acquire from free buffer");
	assert(!m_FreeIds.empty() && "No free handle data ids");

	id = m_FreeIds.front();

	switch(m_Type) {
	case poolBuffer:
		assert(sizeof(DATA) <= m_PoolBuffer->GetElementSize() && "Data bigger than element size");
		pointer = m_PoolBuffer->New();
		break;
	case stackBuffer:
		pointer = m_StackBuffer->alloc(sizeof(DATA),StackMemorySystem::LOWER);
		break;
	case managedPoolBuffer:
		pointer = m_ManagedBuffer->New(sizeof(DATA));
		break;
	}

	assert(pointer != 0 && "Acquire Failed");

	DATA* dataPointer = new (pointer) DATA();

	m_FreeIds.pop_front();	
	m_IdToPointer[id] = pointer;
	
	return dataPointer;
}

template<typename DATA> DATA* BufferWrapper::dereference(dataId_t id, unsigned int magicNumber) {
	assert(!isFree() && "Dereferencing from a free buffer");
	assert(id < m_IdToPointer.size() && "Handle id overflow");
	assert(m_MagicNumbers[id] == magicNumber && "Incorrect magic number");

	return static_cast<DATA*>(m_IdToPointer[id]);
}

#endif

